home *** CD-ROM | disk | FTP | other *** search
- #import <appkit/NXImage.h>
- #import <ctype.h>
- #import <dpsclient/wraps.h>
- #import <math.h>
- #import <appkit/graphics.h>
- #import "TetMatrix.h"
- #import "Piece.h"
- #import "ScoreKeeper.h"
- #import "TetApp.h"
- #import "wraps.h"
-
- // The number of pixels a block moves every step
- // Make it a divisor of the block size
- static const float ANM_DELTA = 8.0;
-
- const float GAMEOVER_BMAP_H = 100.0;
-
- extern long random();
- extern void srandom(int seed);
- extern int getpid();
-
- @implementation TetMatrix
-
- /*
- * Some things should be set differently for color machines for either
- * performances or aesthetic reasons.
- */
- - initForColor
- {
- const NXScreen *deepestScreen;
-
- deepestScreen=[NXApp colorScreen];
-
- if ( deepestScreen->depth == NX_TwoBitGrayDepth ) {
- anm_gameover_delta = 1.0;
- } else {
- anm_gameover_delta = 2.0;
- }
- return self;
- }
-
-
- - initFrame:(const NXRect *)frameRect
- {
- NXSize size, interCell;
- extern BOOL resize(NXSize *aSize);
- float viewWidth, viewHeight;
-
- // Set the # of rows and columns in the view
- [super initFrame:frameRect numRows:TETRIS_ROWS numCols:TETRIS_COLUMNS];
- [self setBackgroundGray:NX_BLACK];
-
- size.width = size.height = 6.0;
- [self setInset:&size];
-
- // Set the amount of space to leave b/w each block
- interCell.width = interCell.height = 1.0; // 1 pixel
- [self setIntercell:&interCell];
-
- pieceVisible = active = NO;
- thePiece = [[Piece alloc] init];
-
- [thePiece getBlockSize:&size];
-
- // Should only resize in Piece.m
- resize(&size); // Make sure block image isn't too large
- [super setElementSize:&size];
-
- // Size Game View.
- // Default view size is (182, 438)
- // Getting (176, 416)
- viewWidth = 6. + TETRIS_COLUMNS * (size.width + interCell.width) + 6.;
- viewHeight = 6. + (TETRIS_ROWS)* (size.height + interCell.height) + 6.;
-
- #ifdef DEBUG
- fprintf(stderr, "Sizing the Tetris view to (%f,%f)\n", viewWidth, viewHeight);
- #endif
- [self sizeTo: viewWidth :viewHeight];
-
- scoreKeeper = nil;
-
- anmBitmap = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
-
- // Eventually want the background to be setable
-
- // anmBitmap = [NXImage initSize:&bounds.size];
- // [anmBitmap useFromFile:"Snow.tiff"];
- showNext = nil;
- [self initForColor];
-
- gameRunning = NO;
- srandom(getpid()); // Seed random # generator
-
- return self;
- }
-
- - setupRandomFill:(int)max
- {
- int blockNum;
- int row, col;
-
- if (max > TETRIS_ROWS) max = TETRIS_ROWS;
- if (max > 10) max = 10;
- // fprintf (stderr, "Start random fill ...\n");
- for (row = 0; row < max; row++) {
- for (col = 0; col < TETRIS_COLUMNS; col++) {
- if ((random () & 0x3) == 0) { /* set 1/4 */
- blockNum = (random () & 0x3); // Rand num 0..3
- [super setBitmap: [thePiece getBlockImage:blockNum] at:row :col];
- }
- }
- }
- return self;
- }
-
-
- - (BOOL)acceptsFirstResponder
- {
- return active;
- }
-
- - drawSelf:(const NXRect *)rects :(int)rectCount
- {
- [super drawSelf:rects :rectCount];
- if (pieceVisible) {
- NXRectClip(&insetBounds);
- [thePiece draw:self];
- }
- return self;
- }
-
-
- /*
- * Act upon keyboard input from the user.
- *
- */
- - keyDown:(NXEvent *)theEvent
- {
- unsigned short charCode;
-
- charCode = theEvent->data.key.charCode;
- if (isupper(charCode))
- charCode = tolower(charCode);
-
- switch (charCode) {
- case 'k':
- case '5':
- if (charCode == 'k' || theEvent->flags & NX_NUMERICPADMASK)
- [thePiece turn:self];
- break;
- case 'j':
- case '4':
- if (charCode == 'j' || theEvent->flags & NX_NUMERICPADMASK)
- [thePiece left:self];
- break;
-
- case 'l':
- case '6':
- if (charCode == 'l' || theEvent->flags & NX_NUMERICPADMASK)
- [thePiece right:self];
- break;
-
- case ' ':
- case '2':
- case '0':
- if (charCode == ' ' || theEvent->flags & NX_NUMERICPADMASK) {
- [self stopTimedEntry];
- [thePiece drop:self];
- [self startTimedEntry];
- }
- break;
-
- case 13: // Pause if the user hits return
- [NXApp pause:self];
- }
- return self;
- }
-
-
- - setPieceVisible:(BOOL)flag
- {
- pieceVisible = flag;
- return self;
- }
-
- - setScoreKeeper:keeper
- {
- scoreKeeper = keeper;
- return self;
- }
-
- /*
- * Determine how fast a piece will drop.
- */
- -(double) newDelay
- {
- return 0.5 - 0.1 * sqrt(2.5 * level);
- }
-
- - newGame:(int)theLevel
- {
- [[super setBitmap:nil] display];
- [window makeFirstResponder:self];
- [self setPieceVisible:YES];
- [thePiece reset:self
- piece:((showNext) ? [showNext pieceInfo] : PIECE_INFO_NULL)];
-
- [self setupRandomFill:[randomFields intValue]];
- [self display];
- level = (float)theLevel;
- teDelay = [self newDelay];
- [self startTimedEntry];
- active = YES;
- return self;
- }
-
- - pause:sender
- {
- // Make TetApp the first responder
- [window makeFirstResponder:window];
- // Will making this object the first responder cause problems?
- // [window makeFirstResponder:self];
- active = NO;
- return [self stopTimedEntry];
- }
-
-
- - continue:sender
- {
- active = YES;
- [window makeFirstResponder:self];
- return [self startTimedEntry];
- }
-
- - stop:sender
- {
- active = NO;
- [window makeFirstResponder:window];
- return [self stopTimedEntry];
- }
-
- /*
- * Scroll "Game Over" down the screen after the game is finished.
- */
- - animateGameOver
- {
- NXSize tmpSize;
- id anmGameOver;
- NXRect destRect, unionRect;
-
- tmpSize.height = GAMEOVER_BMAP_H;
- tmpSize.width = insetBounds.size.width;
- anmGameOver = [[NXImage alloc] initSize:&tmpSize];
-
- // Composite "Game Over" to a window
- [anmGameOver lockFocus];
- PSsetalpha(0.0);
- PSrectfill(0.0, 0.0, insetBounds.size.width, GAMEOVER_BMAP_H);
- PSsetalpha(1.0);
- PSgameover(insetBounds.size.width);
- [anmGameOver unlockFocus];
-
- [super getRect:&destRect for:(int)(TETRIS_ROWS / 2) :0];
- [super getRect:&unionRect for:TETRIS_ROWS - 1 :TETRIS_COLUMNS - 1];
- NXUnionRect(&unionRect, &destRect);
-
- [anmBitmap lockFocus];
- [self drawSelf:&destRect :1];
- [anmBitmap unlockFocus];
-
- NXSetRect(&unionRect, destRect.origin.x, unionRect.origin.y,
- destRect.size.width, GAMEOVER_BMAP_H);
- [self lockFocus];
- NXRectClip(&destRect);
-
- while (unionRect.origin.y - anm_gameover_delta >= destRect.origin.y) {
- unionRect.origin.y -= anm_gameover_delta;
-
- [anmGameOver composite:NX_SOVER toPoint:&unionRect.origin];
- [[self window] flushWindow];
- NXPing();
- // Redraw the background to erase the "Game Over" letters
- // [anmBitmap composite:NX_COPY fromRect:&unionRect toPoint:&unionRect.origin];
- [anmBitmap composite:NX_SOVER fromRect:&unionRect toPoint:&unionRect.origin];
- NXPing();
- }
- [anmGameOver composite:NX_SOVER toPoint:&unionRect.origin];
- [self unlockFocus];
-
- [anmGameOver free];
- return self;
- }
-
- /* The following methods:
- * Animate a piece dropping.
- * Dissolve filled rows.
- * Removes filled rows from the game.
- */
-
- - (BOOL) rowFilled:(int)row
- {
- int xc;
-
- for (xc = 0; xc < TETRIS_COLUMNS; xc++)
- if (![super bitmapAt:row :xc])
- break;
- return (xc == TETRIS_COLUMNS);
- }
-
- - (BOOL) rowEmpty:(int) row
- {
- int xc;
-
- for (xc = 0; xc < TETRIS_COLUMNS; xc++)
- if ([super bitmapAt:row :xc])
- break;
- return (xc == TETRIS_COLUMNS);
- }
-
- - fadeFilledRows:(int) from :(int) to
- {
- float gc;
- NXRect destRect;
- NXRect unionRect;
-
- [super getRect:&destRect for:from :0];
- [super getRect:&unionRect for:to :TETRIS_COLUMNS - 1];
- NXUnionRect(&unionRect, &destRect);
-
- // Composite the rows we are about to fade to anmBitmap
- [anmBitmap lockFocus];
- [self drawSelf:&destRect :1];
- [anmBitmap unlockFocus];
-
- // Fade the blocks
- [self lockFocus];
- for (gc = 1.0; gc > 0.3; gc -= .03) {
- PSsetgray(gc);
- PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
- destRect.size.height, NX_PLUSD);
- [[self window] flushWindow];
- // Now redraw the blocks so we can fade them a little more
- // [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&destRect.origin];
- [anmBitmap composite:NX_SOVER fromRect:&destRect toPoint:&destRect.origin];
- NXPing();
- }
- // Now make the blocks totally disappear.
- PSsetgray(NX_BLACK);
- PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
- destRect.size.height, NX_SOVER);
- // destRect.size.height, NX_COPY);
-
- [self unlockFocus];
-
- return self;
- }
-
- /*
- * Move the pieces as they are moved down on the screen after one or more rows
- * has been cleared.
- */
- - animateDrop:(int) firstFullRow :(int)firstNonFullRow :(int) lastNonEmptyRow
- {
- NXPoint dPt;
- NXRect destRect;
- NXRect unionRect;
-
- [super point:&dPt for:firstFullRow :0]; // Place where to put falling blocks
- [super getRect:&destRect for:firstNonFullRow :0]; // First row to move down
- // Last row to move down
- [super getRect:&unionRect for:lastNonEmptyRow :TETRIS_COLUMNS - 1];
-
- // We want to move everything down from the last non-full row to
- // the last row with a piece in it.
-
- NXUnionRect(&unionRect, &destRect);
- unionRect = destRect;
-
- // Create the image of the blocks
- [anmBitmap lockFocus];
- [self drawSelf:&destRect :1];
- [anmBitmap unlockFocus];
-
- [self lockFocus];
-
- PSsetgray(NX_BLACK); // The destination Rect is Black
- PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
- destRect.size.height, NX_COPY);
-
-
- while (unionRect.origin.y - ANM_DELTA > dPt.y) {
-
- unionRect.origin.y -= ANM_DELTA;
-
- // First clear the window
- [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&unionRect.origin];
- [[self window] flushWindow];
- PScompositerect(unionRect.origin.x, unionRect.origin.y, unionRect.size.width,
- unionRect.size.height, NX_COPY);
- NXPing();
- }
- [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&dPt];
- [[self window] flushWindow];
-
- // NXPing();
- [self unlockFocus];
- return self;
- }
-
- /*
- * Remove the filled rows after they have dissolved
- */
- - removeFilledRows
- {
- BOOL filled;
- int ycfrom, ycto, yctop;
- int xc, yc;
- int lastFilledRow, lastNonEmptyRow, firstFilled;
- int piecey = [thePiece getCurRow]; // Get the bottom-most row of the piece.
-
- do {
- filled = NO; // This should only execute once?!?!
-
- // Work our way up from the current piece's row.
- for (ycfrom = piecey; ycfrom < piecey + MAX_SHAPE_SIZE; ycfrom++) {
- if ([self rowFilled: ycfrom]) {
- filled = YES;
- firstFilled = ycfrom;
- break;
- }
- }
-
- if (filled) {
-
- // Find the first row that isn't completely filled.
- lastFilledRow = firstFilled; // Loop can execute 0 times
- for (ycto = firstFilled + 1; ycto < firstFilled + MAX_SHAPE_SIZE; ycto++) {
- if ([self rowFilled: ycto]) {
- lastFilledRow = ycto;
- } else {
- break;
- }
- }
- // Find the topmost row in the program. A row with at least one block.
- // Assert: Must enter loop at least once.
- for (yctop = lastFilledRow+1; yctop < TETRIS_ROWS; yctop++) {
- if (! [self rowEmpty: yctop]) {
- lastNonEmptyRow = yctop; // But do I have to execute this once?
- } else {
- break;
- }
- }
- [self fadeFilledRows: firstFilled :lastFilledRow];
-
- // Animate the all of the pieces as the move down.
- [self animateDrop:firstFilled :lastFilledRow+1 :lastNonEmptyRow];
-
-
- // Move the rows above the faded rows down to take their place.
-
- for (yc = lastFilledRow+1; yc <=lastNonEmptyRow; yc++) {
- for (xc = 0; xc < TETRIS_COLUMNS; xc++) // For each block in the row.
- [super setBitmap:[self bitmapAt:yc :xc] at:firstFilled :xc];
- firstFilled++;
- }
-
- // Clear the rows at the very top that have been moved down
- // and which have nothing to take their place.
-
- for (yc = firstFilled; yc <=lastNonEmptyRow; yc++) {
- #ifdef DEBUG
- printf("Removing row %d\n", yc);
- #endif
- for (xc = 0; xc < TETRIS_COLUMNS; xc++)
- [super setBitmap:nil at:yc :xc];
- }
- piecey++;
- }
- } while (filled);
- return self;
- }
-
-
- /* The main animation routines follow. A timed entry routine is setup
- * so that it calls teHandler at a regular interval (depending on the
- * level)
- */
-
- /*
- * The timed entry handler that gets called every teDelay seconds.
- *
- */
- - step
- {
- static int downWait = 0;
-
- // Move the piece down. If it can't move down make it stick,
- // remove filled rows, and generate a new piece.
-
- if (![thePiece down:self]) {
-
- // After the piece sticks to the bottom, wait for a while before
- // we send the next piece. The wait depends on the current level.
-
- if (++downWait >= level) { // Don't commit the piece immediately
- downWait = 0;
- [self stopTimedEntry];
- [self setPieceVisible:NO];
- [thePiece stick:self]; // Make the piece stick
- [self removeFilledRows];
- [self setPieceVisible:YES];
- if (scoreKeeper) {
- [scoreKeeper addScore:[thePiece points]];
- level += (MAX_LEVEL - level) / 400.0;
- teDelay = [self newDelay];
- }
- // show the next piece.
- if ([thePiece reset:self piece:((showNext) ?
- [showNext pieceInfo] : PIECE_INFO_NULL)])
- [self startTimedEntry];
- else {
- [self animateGameOver];
- [[self window] makeFirstResponder:[self window]];
- active = NO;
- [NXApp gameOver];
- }
- }
-
- } else { // The piece can't go down
- downWait = 0; // Set a delay before showing next piece
- }
- return self;
- }
-
- static void runOneStep (DPSTimedEntry timedEntry, double timeNow, void *data)
- {
- [(id)data step];
- }
-
- - startTimedEntry
- {
- if (!gameRunning) {
- timedEntryNum = DPSAddTimedEntry(teDelay,&runOneStep,
- self, NX_BASETHRESHOLD);
- gameRunning = YES;
- }
- return self;
- }
-
- - stopTimedEntry
- {
- if (gameRunning) {
- DPSRemoveTimedEntry(timedEntryNum);
- gameRunning = NO;
- }
- return self;
- }
-
- /*
- * Clean up.
- */
- - free
- {
- [thePiece free];
- [anmBitmap free];
- return [super free];
- }
-
- @end
-